from django.db.models.fields import FieldDoesNotExist

from history.helpers import invalid_fields, iter_original_fields


def add_model_to_archive(sender, instance, signal, created):
    if created:
        instance._history_new()
    else:
        def check_func(latest):
            return any(f.value_from_object(instance) != f.value_from_object(latest)
                for f in instance._meta.fields
                if hasattr(latest, f.attname) and not isinstance(f, invalid_fields))
        def update_func(latest):
            for f in iter_original_fields(instance):
                if hasattr(latest, f.attname):
                    setattr(latest, f.attname, f.value_from_object(instance))
        instance._history(check_func, update_func)


def reversed(signal_func):
    def res_func(**kwargs):
        try:
            kwargs['instance']._meta.get_field(kwargs['field_name'])
        except FieldDoesNotExist:
            kwargs['reversed'] = True
        else:
            kwargs['reversed'] = False
        signal_func(**kwargs)
    return res_func


@reversed
def add_m2m_to_archive(signal, sender, instance, field_name, objs, reversed=False):
    if reversed:
        new_objs = (instance, )
        for new_instance in objs:
            add_m2m_to_archive(signal=signal, sender=sender,
                               instance=new_instance,
                               field_name=field_name, objs=new_objs)
    else:
        check_func = lambda latest: any(obj not in getattr(latest, field_name).all() for obj in objs)
        update_func = lambda latest: getattr(latest, field_name).add(*objs)
        instance._history(check_func, update_func)


@reversed
def remove_m2m_from_archive(signal, sender, instance, field_name, objs, reversed=False):
    if reversed:
        new_objs = (instance, )
        for new_instance in objs:
            remove_m2m_from_archive(signal=signal, sender=sender,
                                    instance=new_instance,
                                    field_name=field_name, objs=new_objs)
    else:
        check_func = lambda latest: any(obj in getattr(latest, field_name).all() for obj in objs)
        update_func = lambda latest: getattr(latest, field_name).remove(*objs)
        instance._history(check_func, update_func)


@reversed
def clear_m2m_from_archive(signal, sender, instance, field_name, reversed):
    if reversed:
        new_objs = (instance, )
        instances = sender.objects.filter(**{field_name: instance})
        for new_instance in instances:
            remove_m2m_from_archive(signal=None, sender=sender,
                                    instance=new_instance,
                                    field_name=field_name, objs=new_objs)
    else:
        check_func = lambda latest: bool(getattr(latest, field_name))
        update_func = lambda latest: getattr(latest, field_name).clear()
        instance._history(check_func, update_func)
